<?php
namespace DRY\Module;

/**
 * DonReY Framework 2012 :: This module is used to control DonReY itself (install and configure, create and destroy)
 * This module is used extensively by DonReY's frontend management component, and normally should be used by an application directly, unless the application itself is a CMS
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Structure/Modules/control.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 */
class Control extends base
{
	const
		EXC_UPDATE_INPUT_UNRECOGNISED = 1;

	/**
	 * Scans the configuration and returns a list of configured modules and under what alias, if they are "autoloaded" and what other modules they depend on
	 */
	public function config_Scan_Modules()
	{
		$active_modules = array();
		foreach(\DRY\core::$CFG-> core-> modules-> children() as $module_CFG) {
			$active_modules[] = array(
				'module'=> $module_CFG-> getName(),
				'alias'=> isset($module_CFG['alias'])?(string)$module_CFG['alias']:$module_CFG-> getName(),
				'autoload'=> isset($module_CFG['autoload']) && $module_CFG['autoload'] == true,
				'depends'=> isset($module_CFG['depends'])?explode(',', $module_CFG['depends']):null
			);
		}
		return $active_modules;
	}

	/**
	 * Reads the current configuration for a module alias
	 * @param string $alias
	 */
	public function config_Read_Alias($alias)
	{
		foreach(\DRY\core::$CFG-> core-> modules-> children() as $module_CFG) {
			if(isset($module_CFG['alias'])) {
				if($module_CFG['alias']==$alias)
					return $module_CFG;
			} else
				if($module_CFG-> getName()==$alias)
					return $module_CFG;
		}
	}

	/**
	 * Adds the minimum required configuration to use a module to this application's Config
	 * @param string $module
	 * @param string $alias
	 * @param mixed $input Data for the "setup_Config" method of the module
	 * @param bool $autoload If this module will be autoloaded by the framework or not
	 */
	public function config_Install_Module($module, $alias = null, $input = null, $autoload = false)
	{
		// check if config already contains the combination of $module ( $alias )
		if(\DRY\core::$CFG-> core-> modules-> $module) {
			// try all configuration elements under this module's name - If any match to $alias exist, return
			foreach(\DRY\core::$CFG-> core-> modules-> $module as $module_CFG) {
				if(is_null($alias) && !isset($module_CFG['alias']))
					return false; // error, already in use
				elseif($alias == $module_CFG['alias'])
					return false; // error, already in use
			}
		}

		// create the SimpleXMLElement object which will contain the module's configuration
		$attrs = array();
		if(!is_null($alias))
			$attrs['alias'] = $alias;
		if($autoload)
			$attrs['autoload'] = 'true';
		$CFG = \SimpleXMLPlus::create_Instance($module, $attrs);

		// Attempt to use the module itself to add the proper configuration elements to this object
		$module_class = 'Module_'.ucfirst($module);
		if(class_exists($module_class, true) && method_exists($module_class, 'setup_Config')) {
			$module_class::setup_Config($CFG, $input);
		}

		// append the new module configuration to \DRY\core::$CFG
		\DRY\core::$CFG-> core-> modules-> add_XML_Child($CFG);

		$this-> config_Write();
	}

	/**
	 * Updates a module configuration with new content.
	 * If the module has an "update_Config" method, the $input is passed to it, and what it returns is saved in the config.
	 * If not, but the $new Content is SimpleXMLElement, that content is used as new configuration for the module
	 *
	 * @param string $alias
	 * @param mixed $input
	 * @param bool $autoload (optional) Used to change the value of the "autoload" attribute.
	 */
	public function config_Update_Alias($alias, $input = null, $autoload = null)
	{
		// a little anon method that creates configuration
		$build_module_cfg = function(&$CFG) use ($input, $autoload)
		{
			// autoload change
			if(is_bool($autoload)) {
				if($autoload)
					$CFG['autoload'] = 'true';
				else
					unset($CFG['autoload']);
			}

			// use the updater method if it exists
			$module_class = '\DRY\Module\\'.ucfirst($CFG-> getName());
			if(class_exists($module_class, true) && method_exists($module_class, 'update_Config')) {
				return $module_class::update_Config($CFG, $input);
			} elseif(is_null($input))
				// nothing in the content changed
				return $CFG;
			elseif($input instanceof \SimpleXMLElement) {
				// content completely overwritten
				if(is_bool($autoload) && $autoload)
					$input['autoload'] = 'true';
				return $input;
			}
		};

		// it is not possible to replace an element inside the iterator, so reconstruct the "modules" object without the one that is being deleted
		$new_CFG = clone(\DRY\core::$CFG);
		$new_CFG-> core-> modules = null;
		// search configuration for this alias, or a module which has no alias but has the name {$alias}
		foreach(\DRY\core::$CFG-> core-> modules-> children() as $module_CFG) {
			if((isset($module_CFG['alias']) && $module_CFG['alias']==$alias) || (!isset($module_CFG['alias']) && $module_CFG-> getName()==$alias)) {
				$module_CFG = $build_module_cfg($module_CFG);
				if(!$module_CFG instanceof \SimpleXMLElement)
					throw new \DRY\Water('control', self::EXC_UPDATE_INPUT_UNRECOGNISED);
			}
			// original or changed, copy it into the $new_CFG
			$new_CFG-> core-> modules-> add_XML_Child($module_CFG);
		}

		$this-> config_Write($new_CFG);
	}

	/**
	 * Removes a module alias from the configuration. Application will no longer be able to use the module with this alias after this method is called
	 * This method doesn't affect the currently loaded configuration ( \DRY\core::$CFG ), so modules can still function for this run.
	 * @param string $alias
	 */
	public function config_Destroy_Alias($alias)
	{
		// it is not possible to delete an element inside the iterator, so reconstruct the "modules" object without the one that is being deleted
		$new_CFG = clone(\DRY\core::$CFG);
		$new_CFG-> core-> modules = null;
		// search configuration for this alias, or a module which has no alias but has the name {$alias}
		foreach(\DRY\core::$CFG-> core-> modules-> children() as $module_CFG) {
			if(isset($module_CFG['alias'])) {
				if($module_CFG['alias']==$alias)
					continue;
			} else
				if($module_CFG-> getName()==$alias)
					continue;

			$new_CFG-> core-> modules-> add_XML_Child($module_CFG);
		}

		$this-> config_Write($new_CFG);
	}

	/**
	 * Writes the configuration file using the \DRY\core::$CFG or the passed $CFG
	 * @param SimpleXMLElement $CFG
	 */
	private function config_Write(&$CFG = null)
	{
		$rel = (string)($this-> CFG-> {'config-file'}['rel']?:'APP_PATH');
		$config_file = constant($rel).$this-> CFG-> {'config-file'};

		XML::init_XMLWriter($config_file, 'config', true, true);
		XML::from_SimpleXML($CFG?:\DRY\core::$CFG);
		XML::write_XML();
	}

	/**
	 * Run a Control Command in a module (if it exists)
	 *
	 * @param string $alias
	 * @param mixed $command
	 * @param mixed $command_data
	 */
	public function module_Command($alias, $command, $command_data = null)
	{
		$module = \DRY\core::m($alias);
		if(method_exists($module, "control_Command"))
			return $module-> control_Command($command, $command_data);
	}

	/**
	 * Well, even the control module can be controller, right ?
	 * @param string $command
	 */
	public function control_Command($command)
	{
		switch($command) {
			case 'ExportConfig':
				$exp = array('module'=> array('alias'=> $this->_alias));
				$rel = (string)($this-> CFG-> {'config-file'}['rel']?:'APP_PATH');
				$exp['config-file'] = array(
					(string)$this-> CFG-> {'config-file'},
					'rel'=> $rel,
					'realpath'=> realpath(constant($rel).$this-> CFG-> {'config-file'})
				);
				return $exp;
		}
	}

}